﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EasyCLRBase.AssemblyExplorer;
using EasyCLRBase;
using System.Collections;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.IO;

namespace EasyCLRMisc
{
    [Export(typeof(IClrBase))]
    public class TabledValueFunction : ClrBase
    {
        [SqlFunction(
            DataAccess = DataAccessKind.Read,
            FillRowMethodName = "FillEnumerateDirectory",
            Name = "ext_enumerate_dir",
            TableDefinition = "\r\n\tDirectoryName nvarchar(max),\r\n\tName nvarchar(max),\r\n\tFullName nvarchar(max),\r\n\tSize bigint,\r\n\tIsDirectory bit," +
            "\r\n\tCreationTime datetime,\r\n\tCreationTimeUtc datetime,\r\n\tLastAccessTime datetime,\r\n\tLastAccessTimeUtc datetime,\r\n\tLastWriteTime datetime,\r\n\tLastWriteTimeUtc datetime")]
        public static IEnumerable EnumerateDirectory(SqlString directory, SqlString searchPattern)
        {
            if (searchPattern.IsNull == false)
            {
                foreach (String entry in Directory.GetFiles(directory.Value, searchPattern.Value).Union(Directory.GetDirectories(directory.Value, searchPattern.Value)).OrderBy(a => a))
                    yield return entry;
            }
            else
            {
                foreach (String entry in Directory.GetFiles(directory.Value).Union(Directory.GetDirectories(directory.Value)).OrderBy(a => a))
                    yield return entry;
            }
        }

        private static void FillEnumerateDirectory(
            Object input,
            out SqlString directoryName,
            out SqlString name, 
            out SqlString fullName, 
            out SqlInt64 size, 
            out SqlBoolean isDirectory,
            out SqlDateTime creationTime,
            out SqlDateTime creationTimeUtc,
            out SqlDateTime lastAccessTime,
            out SqlDateTime lastAccessTimeUtc, 
            out SqlDateTime lastWriteTime,
            out SqlDateTime lastWriteTimeUtc)
        {
            String entry = input.ToString();
            if (File.Exists(entry) == true)
            {
                var a = new FileInfo(entry);
                name = a.Name;
                fullName = a.FullName;
                size = a.Length;
                creationTime = a.CreationTime.ToSqlValue();
                creationTimeUtc = a.CreationTimeUtc.ToSqlValue();
                directoryName = a.DirectoryName.ToSqlValue();
                lastAccessTime = a.LastAccessTime.ToSqlValue();
                lastAccessTimeUtc = a.LastAccessTimeUtc.ToSqlValue();
                lastWriteTime = a.LastWriteTime.ToSqlValue();
                lastWriteTimeUtc = a.LastWriteTimeUtc.ToSqlValue();
                isDirectory = false;
            }
            else if (Directory.Exists(entry) == true)
            {
                var a = new DirectoryInfo(entry);
                name = a.Name;
                fullName = a.FullName;
                size = SqlInt64.Null;
                creationTime = a.CreationTime.ToSqlValue();
                creationTimeUtc = a.CreationTimeUtc.ToSqlValue();
                directoryName = SqlString.Null;
                lastAccessTime = a.LastAccessTime.ToSqlValue();
                lastAccessTimeUtc = a.LastAccessTimeUtc.ToSqlValue();
                lastWriteTime = a.LastWriteTime.ToSqlValue();
                lastWriteTimeUtc = a.LastWriteTimeUtc.ToSqlValue();
                isDirectory = true;

            }
            else
                throw new NotImplementedException("Could not recognize: file or directory.");
        }

        [SqlFunction(
            DataAccess = DataAccessKind.Read,
            FillRowMethodName = "FillSplitString",
            Name = "ext_split",
            TableDefinition = "\r\n\tid int,\r\n\tvalue nvarchar(max)")]
        public static IEnumerable SplitString(SqlString myString, SqlString separator, SqlInt32 limitation)
        {
            String[] result = new String[0];
            if (myString.IsNullOrEmpty() == false)
            {
                if (separator.IsNullOrEmpty() == true)
                    separator = Environment.NewLine;

                if (limitation.IsNull == false && limitation.Value > 0)
                    result = myString.Value.Split(new String[] { separator.Value }, limitation.Value, StringSplitOptions.None);
                else
                    result = myString.Value.Split(new String[] { separator.Value }, StringSplitOptions.None);
            }

            var res = new SplitStruct(0);
            foreach (var str in result)
                yield return res.Next(str.ToSqlValue());
        }

        private static void FillSplitString(Object o, out SqlInt32 index, out SqlString value)
        {
            var item = (SplitStruct)o;

            index = item.Index;
            value = item.Value;
        }


        [SqlFunction(
            FillRowMethodName = "FillSplitStringToTable",
            Name = "ext_split_table",
            TableDefinition = "\r\n\tid int," +
                "\r\n\tcol1 nvarchar(max),\r\n\tcol2 nvarchar(max),\r\n\tcol3 nvarchar(max),\r\n\tcol4 nvarchar(max),\r\n\tcol5 nvarchar(max)," +
                "\r\n\tcol6 nvarchar(max),\r\n\tcol7 nvarchar(max),\r\n\tcol8 nvarchar(max),\r\n\tcol9 nvarchar(max),\r\n\tcol10 nvarchar(max)")]
        public static IEnumerable SplitStringToTable(SqlString myString, SqlString row, SqlString column)
        {
            foreach (SplitStruct item in SplitString(myString, row, -1))
                yield return item.AddColumnSeparator(column.ToNativeValue(), 10);
        }

        private static void FillSplitStringToTable(Object o, out SqlInt32 index,
            out SqlString col1, out SqlString col2, out SqlString col3, out SqlString col4, out SqlString col5, 
            out SqlString col6, out SqlString col7, out SqlString col8, out SqlString col9, out SqlString col10)
        {
            var item = (SplitStruct)o;

            var line = item.Value.ToNativeValue();
            if (line == null)
            {
                col1 = col2 = col3 = col4 = col5 = col6 = col7 = col8 = col9 = col10 = SqlString.Null;
            }
            else
            {
                var list = line.Split(new String[] { item.ColumnSeparator }, item.ColumnCount, StringSplitOptions.None);
                Array.Copy(list, item.Columns, list.Length);
                col1 = item.Columns[0].ToSqlValue();
                col2 = item.Columns[1].ToSqlValue(); 
                col3 = item.Columns[2].ToSqlValue();
                col4 = item.Columns[3].ToSqlValue();
                col5 = item.Columns[4].ToSqlValue();
                col6 = item.Columns[5].ToSqlValue();
                col7 = item.Columns[6].ToSqlValue();
                col8 = item.Columns[7].ToSqlValue();
                col9 = item.Columns[8].ToSqlValue();
                col10 = item.Columns[9].ToSqlValue();
            }

            index = item.Index;
        }


        public struct SplitStruct
        {
            public int Index;
            public SqlString Value;
            public String ColumnSeparator;
            public int ColumnCount;
            public String[] Columns;
            private System.ComponentModel.BackgroundWorker _Worker;
            public String[] AsyncColumns;

            public SplitStruct(int initIndex)
            {
                this.Index = initIndex;
                this.Value = SqlString.Null;
                this.ColumnSeparator = null;
                this.ColumnCount = 0;
                this.Columns = null;
                this.AsyncColumns = null;
                this._Worker = null;
            }

            public SplitStruct AddColumnSeparator(String separator, int colCount)
            {
                this.ColumnSeparator = separator;
                this.ColumnCount = colCount;
                this.Columns = new String[colCount];
                return this;
            }

            public SplitStruct Next(SqlString value)
            {
                this.Index++;
                this.Value = value;

                return this;
            }

            public SplitStruct NextAsync(SqlString value)
            {
                var result = this.Next(value);

                this._Worker = new System.ComponentModel.BackgroundWorker();
                this._Worker.DoWork += new System.ComponentModel.DoWorkEventHandler(this.DoWork);
                this._Worker.RunWorkerAsync();
                return result;
            }

            public void Wait()
            {
                while (this._Worker.IsBusy)
                    System.Threading.Thread.Sleep(1);
            }

            private void DoWork(Object o, System.ComponentModel.DoWorkEventArgs e)
            {
                var list = this.Value.ToNativeValue().Split(new String[] { this.ColumnSeparator }, this.ColumnCount, StringSplitOptions.None);
                Array.Copy(list, this.Columns, list.Length);
            }
        }
    }
}
